/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package core;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import logging.LOG;

/**
 *
 * @author Jorge
 */
public abstract class AbstractCoreServer implements Runnable {

    private int port = 3000;
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private int timeOut = 500; //todo: fazer timeout de clientes
    private int selectorTimeOut = 500;
    private Thread selectorThread;
    private String name="thread" ;

    private List<ProtocolSD.Operation> unJoinParam=new LinkedList<ProtocolSD.Operation>();

    public AbstractCoreServer() {
    }

    public void setPort(int port) {
        if (serverSocketChannel != null) {
            throw new RuntimeException("Server is running");
        }
        this.port = port;

    }

public void addUnJoinParam(ProtocolSD.Operation operation){
        unJoinParam.add(operation);
    }

    public void addUnJoinParam(ProtocolSD.Operation...operation){
        unJoinParam.addAll(Arrays.asList(operation));
    }

    

    public void disconnect() {
        try {
            selectorThread.interrupt();
            serverSocketChannel.close();
            selector.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            serverSocketChannel = null;
            selector = null;
        }
    }

    public void disconnectKey(SelectionKey key) throws IOException {
        key.cancel();
        key.channel().close();
    }

    public void connect() {
        try {
            runLoop();
        } catch (IOException ex) {
            // TODO 
            Logger.getLogger(AbstractCoreServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void connect(int port) {
        if (serverSocketChannel != null) {
            disconnect();
        }
        setPort(port);
        connect();
    }

    protected void runLoop() throws IOException {
        if (serverSocketChannel != null) {
            disconnect();
        }

        selector = Selector.open();
        selectorThread = new Thread(this);
        selectorThread.setName(name);
        selectorThread.start();
    }

    public void setName(String name){
        this.name= name;
    }

    public void run() {
        try {
            serverSocketChannel = SelectorProvider.provider().openServerSocketChannel();
            serverSocketChannel.configureBlocking(false);
            LOG.append("Servidor ligado "+serverSocketChannel.socket().getInetAddress()+":"+serverSocketChannel.socket().getLocalPort());
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);


            while (selector.isOpen() && !selectorThread.isInterrupted()) {
                int n = selector.select(selectorTimeOut);
                if (n == 0) {
                    continue;
                }
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();

                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    try {
                        if (key.isValid() && key.isAcceptable()) {
                            SocketChannel socket = serverSocketChannel.accept();
                            socket.configureBlocking(false);
                            //Comportamento por omissão ler após aceitar connecção
                            SelectionKey selectionKey = socket.register(selector, SelectionKey.OP_READ);
                            ProtocolSD protocolSD = new ProtocolSD(createInstance(), selectionKey);
                            protocolSD.setUnJoinParam(unJoinParam);
                            //adiciona um handler para tratamento de mensagens e um listener para ficar á escuta
                            selectionKey.attach(protocolSD);
                        }
                        if (key.isValid() && key.isReadable()) {
                            //executa o handler de mensagens
                            ((ProtocolSD) key.attachment()).handlerRead();
                        }

                        if (key.isValid() && key.isWritable()) {
                            //executa o handler de mensagens
                            ((ProtocolSD) key.attachment()).handlerWrite();
                        }
                    } catch (Throwable throwable) {
                        try {
                            //Erros que ocorram no cliente
                            disconnectKey(key);
                            if (key.attachment() instanceof ProtocolSD) {
                                ProtocolSD protocolSD = (ProtocolSD) key.attachment();
                                if (protocolSD.getListener().isClose()) {
                                    // Acabou ligação
                                } else {
                                    handlerFatalErrorCliente(throwable);
                                }
                            }else{
                                handlerFatalErrorCliente(throwable);
                            }
                        } catch (IOException exception) {
                            handlerFatalErrorCliente(exception);
                        }
                    }
                }
                selector.selectedKeys().clear();
            }
            selector.close();
        } catch (Throwable exception) {
            disconnect();
            handlerServerError(exception);
        } finally {
            selector = null;
            serverSocketChannel = null;
            selectorThread = null;
        }
    }

    /**
     * Erros de servidor
     * 
     * @param exception
     */
    public abstract void handlerServerError(Throwable exception);

    protected abstract ProtocolSD.IProtocolListener createInstance();

    /**
     * Método invocado quando ocorre um erro ao tratar um cliente
     * O cliente já se encontra desconectado após a invocação do método
     *
     */
    public abstract void handlerFatalErrorCliente(Throwable throwable);
}
